﻿// ScrollBarTest.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <windows.h>
#include <winuser.h>
#include <windowsx.h>
#include <strsafe.h>

LRESULT CALLBACK WndProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam);

LRESULT CALLBACK ScrollProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

LRESULT CALLBACK MyTestWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR pCmdLine,int nCmdShow)
{

	static TCHAR szAppName[] = TEXT("scrollcalctest");

	HWND hWnd;

	MSG msg;

	WNDCLASS wndClass;
	wndClass.style = CS_HREDRAW| CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;
	wndClass.hCursor = LoadCursor(NULL,IDC_ARROW);
	wndClass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = szAppName;

	if (!RegisterClass(&wndClass))
	{
		return 0;
	}

	wndClass.style = 0;
	wndClass.lpfnWndProc = ScrollProc;
	wndClass.lpszClassName = TEXT("MyScroll");
	wndClass.hbrBackground = 0;

	if (!RegisterClass(&wndClass))
	{
		return 0;
	}

	hWnd = CreateWindow(szAppName,TEXT("测试滚动条"), WS_OVERLAPPEDWINDOW | WS_VSCROLL,500,200,
		350,250,NULL,NULL, hInstance,NULL);

	ShowWindow(hWnd,nCmdShow);

	UpdateWindow(hWnd);

	while(GetMessage(&msg,NULL,0,0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	// 滚动条句柄
	static HWND hScroll;
	static int nRowCount, nRowHeight, nXClient, nYClient;
	HDC hDC;
	int i, y, nVertPos, nPrintBeg, nPrintEnd;
	PAINTSTRUCT ps;
	SCROLLINFO scrollinfo;
	TCHAR szBuffer[100];
	TEXTMETRIC tm;
	RECT rc;

	switch (uMsg)
	{
	case WM_CREATE:
	{
		// 测试当前数据行数,用于测试滚动量
		nRowCount = 100;
		// 获取一行的高度
		hDC = GetDC(hWnd);
		GetTextMetrics(hDC,&tm);
		// 获取当行的高度
		nRowHeight = tm.tmHeight + tm.tmExternalLeading;
		ReleaseDC(hWnd,hDC);
		// 创建滚动条
		hScroll = CreateWindow(TEXT("MyScroll"), TEXT("MyScroll"),WS_CHILD| WS_VISIBLE| BS_OWNERDRAW,
			300,10,16,300,hWnd,0, ((LPCREATESTRUCT)lParam)->hInstance,NULL);

	    return 0;
	}
	case WM_SIZE:
	{
		// 获取当前窗口的大小
		nXClient = LOWORD(lParam);
		nYClient = HIWORD(lParam);

		// 设置滚动条
		scrollinfo.cbSize = sizeof(scrollinfo);
		scrollinfo.fMask = SIF_PAGE | SIF_RANGE;
		scrollinfo.nMin = 0;

		// 只要超过1行就能滚动,因此需要多加1页的行数 最大滚动范围值:111
		scrollinfo.nMax = nRowCount - 1 + nYClient / nRowHeight - 1;
		// 设置一页的大小13
		scrollinfo.nPage = nYClient / nRowHeight;

		SetScrollInfo(hWnd,SB_VERT, &scrollinfo,TRUE);

		// 修正滚动条的位置
		MoveWindow(hScroll, nXClient-100,10,16, nYClient-20,true);
		
		SendMessage(hScroll,SBM_SETSCROLLINFO,TRUE,(LPARAM)(&scrollinfo));

		return 0;
	}
	case WM_VSCROLL:
	{
		// 获取竖滚动条状态
		scrollinfo.cbSize = sizeof(scrollinfo);
		scrollinfo.fMask = SIF_ALL;
		GetScrollInfo(hWnd, SB_VERT ,&scrollinfo);

		// 保存原来的位置,用于计算滚动当前画布
		nVertPos = scrollinfo.nPos;

		// lParam 参数为滚动条控件,标准滚动条控件没有这个参数
		if (lParam)
			SendMessage(hScroll, SBM_GETSCROLLINFO,NULL,(LPARAM)&scrollinfo);

		// 设置滚动量
		switch (LOWORD(wParam))
		{
		case SB_TOP:
			scrollinfo.nPos = scrollinfo.nMin;
			break;
		case SB_BOTTOM:
			scrollinfo.nPos = scrollinfo.nMax;
			break;
		case SB_LINEUP:
			scrollinfo.nPos -= 1;
			break;
		case SB_LINEDOWN:
			scrollinfo.nPos += 1;
			break;
		case SB_PAGEUP:
			scrollinfo.nPos -= scrollinfo.nPage;
			break;
		case SB_PAGEDOWN:
			scrollinfo.nPos += scrollinfo.nPage;
			break;
		case SB_THUMBTRACK:
			scrollinfo.nPos = scrollinfo.nTrackPos;
			break;
		default:
			break;
		}

	    // 更新系统滚动状态
		scrollinfo.fMask = SIF_POS;
		SetScrollInfo(hWnd,SB_VERT,&scrollinfo,true);
		GetScrollInfo(hWnd, SB_VERT, &scrollinfo);

		// 滚动界面画布
		if (scrollinfo.nPos != nVertPos)
		{
			GetClientRect(hWnd,&rc);

			// 不要刷到滚动条位置,否则会闪
			rc.right -= 150;

			hDC = GetDC(hWnd);
			ScrollDC(hDC,0, nRowHeight*(nVertPos - scrollinfo.nPos),&rc,NULL,NULL,NULL);
			ReleaseDC(hWnd,hDC);

			// 判断是向上滚动还是向下滚动,nVertPos - scrollinfo.nPos > 0表示向上滚动
			// nVertPos - scrollinfo.nPos < 0 : 表示向下滚动
			if (nRowHeight * (nVertPos - scrollinfo.nPos) < 0)
				rc.top = rc.bottom + nRowHeight * (nVertPos - scrollinfo.nPos);
			else
				rc.bottom = rc.top + nRowHeight * (nVertPos - scrollinfo.nPos);

			InvalidateRect(hWnd,&rc,FALSE);
		}

		// 通知自定义滚动条滚动
		SendMessage(hScroll, SBM_SETSCROLLINFO,TRUE,(LPARAM)(&scrollinfo));
					
		return 0;
	}
	case WM_MOUSEWHEEL:
		// 消息发送到滚动条控件
		PostMessage(hScroll, WM_MOUSEWHEEL, wParam, lParam);
		return 0;
	case WM_PAINT:
	{
		hDC = BeginPaint(hWnd, &ps);
		// 获取竖滚动条的状态
		scrollinfo.cbSize = sizeof(scrollinfo);
		scrollinfo.fMask = SIF_ALL;
		GetScrollInfo(hWnd, SB_VERT, &scrollinfo);
		nVertPos = scrollinfo.nPos;

		// 重回修改位置(减少重绘量)
		nPrintBeg = max(0, nVertPos + ps.rcPaint.top/ nRowHeight);
		nPrintEnd = min(nRowCount-1, nVertPos + ps.rcPaint.bottom / nRowHeight);

		// 输出当前行告
		for (i= nPrintBeg;i<= nPrintEnd;i++)
		{
			y = nRowHeight * (i - nVertPos);

			TextOut(hDC, 22, y, szBuffer,wsprintf(szBuffer, TEXT("%5d   "), i + 1));
		}

		// 填充空白区域
		y = nRowHeight * (nPrintEnd + 1 - nVertPos);

		if (ps.rcPaint.bottom - y > 0)
		{
			rc = ps.rcPaint;
			rc.top = y;
			// 使用当前选定的字体、背景色、和文本颜色绘制文本
			ExtTextOut(hDC,0,0, ETO_OPAQUE,&rc,0,0,0);
		}
 
		EndPaint(hWnd, &ps);

		return 0;
	}
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	}
	return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

static void MyCGFillColor(HDC hDC,RECT* pRect,unsigned int Color)
{
	// 实际使用ExtTextOut要比FillRect效果要好,能减少绘制中的闪烁问题
	SetBkColor(hDC, Color);

	ExtTextOut(hDC,0,0, ETO_OPAQUE, pRect,0,0,0);
}

// 计算滚动条的位置 
// 参数:nPos:鼠标位置;pScrollinfo:包含当前滑块信息;nHeight: 滑块的总高度
static int CalculateThumPos(int nPos,SCROLLINFO* pScrollinfo,int nHeight)
{
	// 滑块的大小
	float dbThumSize;
	// 每像素的滑动量
	float dbPxSize;
	// 可滑动数量
	int nScrollCnt;

	// 滑块的尺寸 = 滚动条高度/有效范围 * 每页数量
	// 最小是20
	dbThumSize = max((float)((nHeight / (pScrollinfo->nMax - pScrollinfo->nMin))* pScrollinfo->nPage),20.0f);

	if (nPos <= dbThumSize/2)
	{
		return 0;
	}
	
	if(nPos >= nHeight - (dbThumSize / 2))
	{
		// 返回 可滑动数量
		return (pScrollinfo->nMax - pScrollinfo->nMin + 1) - pScrollinfo->nPage;
	}

	// 计算方法:
	// 每像素滑动量 = 有效高度 / 可滑动量
	// 有效高度 = 滑块总高度 - 滑块大小
	// 可滑动量 = 可滑动总量 - 每页数量
	// 可滑动总量 = 滑块最大值 - 滑块最小值 + 1
	// 有效范围 = 滑块最大值 - 滑块最小值
	
	// 可滑动数量
	nScrollCnt = (pScrollinfo->nMax - pScrollinfo->nMin + 1) - pScrollinfo->nPage;
	// 每像素的滑动量
	dbPxSize = (float)(nHeight - dbThumSize) / (float)nScrollCnt;

	// 计算方法
	// 位置 = (鼠标位置 - 半滑块尺寸)/每像素滑动量
	return (int)((nPos - dbThumSize / 2.0) / dbPxSize);

}


LRESULT CALLBACK ScrollProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	HDC hDC;

	PAINTSTRUCT ps;

	SCROLLINFO* pScrollinfo;

	RECT rect;

	TCHAR szBuffer[100];

	// 滑块的尺寸
	float dbSize;
	// 滑块界面Top位置
	int nTop;
	// 用来保存滚动条信息
	static SCROLLINFO scrollinfo;
	// 拖拽状态
	static int nDragState;
	// 滚动区域高度
	static int nHeight;

	int nAccumDelta;

	// 滚动条设置消息
	// SBM_GETSCROLLINFO SBM_SETSCROLLINFO消息参数
	// wParam:是否要刷新(SBM_GETSCROLLINFO 消息中该参数无用)
	// lParam:SCROLLINFO* 信息

	switch (uMsg)
	{
	case SBM_SETSCROLLINFO:
	{
		if (!lParam)
			return 0;
		// 设置滚动条的信息
		pScrollinfo = (SCROLLINFO*)lParam;
		// 获取当前滚轮的范围
		if (pScrollinfo->fMask & SIF_RANGE)
		{
			// 设置内容行数
			scrollinfo.nMax = pScrollinfo->nMax;
			scrollinfo.nMin = pScrollinfo->nMin;
		}
		// 获取当前页的大小
		if (pScrollinfo->fMask & SIF_PAGE)   // 每页能滚动多少行
			scrollinfo.nPage = pScrollinfo->nPage;
		// 当前显示的位置
		if (pScrollinfo->fMask & SIF_POS)   // 行显示位置
			scrollinfo.nPos = pScrollinfo->nPos;

		wsprintf(szBuffer,TEXT("Y Pos:%3d"), scrollinfo.nPos);

		SetWindowText(GetParent(hWnd), szBuffer);

		if (wParam)
			InvalidateRect(hWnd,NULL,false);
	    return 0;
	}
	case SBM_GETSCROLLINFO:
	{
		if (lParam)
			*((SCROLLINFO*)lParam) = scrollinfo;
		return 0;
	}
	case WM_SIZE:
	{
		nHeight = HIWORD(lParam);
		break;
	}
	case WM_LBUTTONDOWN:
	{
		scrollinfo.nTrackPos = CalculateThumPos(GET_Y_LPARAM(lParam), &scrollinfo, nHeight);

		// by 2023/06/28 将当前滚动值反馈给父窗口滚动条
		if (scrollinfo.nPos != scrollinfo.nTrackPos)
			PostMessage(GetParent(hWnd), WM_VSCROLL, SB_THUMBTRACK, (LPARAM)hWnd);
		nDragState = 1;
		InvalidateRect(hWnd, NULL, false);
		break;
	}
	
	case WM_MOUSEMOVE:
	{
		if (nDragState == 1)
		{
			SetCapture(hWnd);
			nDragState = 2;

		}
		else if(nDragState == 2)
		{
			if (!(wParam & MK_LBUTTON))
			{
			    nDragState = 0;
			    
				if (GetCapture() == hWnd)
					ReleaseCapture();
			}
			else
			{
				scrollinfo.nTrackPos = CalculateThumPos(GET_Y_LPARAM(lParam),&scrollinfo, nHeight);

				if (scrollinfo.nPos != scrollinfo.nTrackPos)
					PostMessage(GetParent(hWnd), WM_VSCROLL, SB_THUMBTRACK, (LPARAM)hWnd);
			}

		}
		break;
	}
	case WM_LBUTTONUP: // 鼠标弹起,滚动到了指定的位置
	{
		if (nDragState == 2)
			ReleaseCapture();

		if (nDragState)
		{
			nDragState = 0;
			InvalidateRect(hWnd, NULL, false);
		}
		break;
	}
	case WM_MOUSEWHEEL:
	{
		nAccumDelta = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA;

		// 每滚一次3行
		scrollinfo.nTrackPos = scrollinfo.nPos - nAccumDelta * 3;

		if (scrollinfo.nTrackPos < 0)
			scrollinfo.nTrackPos = 0;
		else if (scrollinfo.nTrackPos > ((int)((scrollinfo.nMax - scrollinfo.nMin + 1) - scrollinfo.nPage)))
			scrollinfo.nTrackPos = (scrollinfo.nMax - scrollinfo.nMin + 1) - scrollinfo.nPage;

		// by 2023/06/28 将当前滚动值反馈给父窗口滚动条
		if (scrollinfo.nTrackPos != scrollinfo.nPos)
			PostMessage(GetParent(hWnd), WM_VSCROLL, SB_THUMBTRACK, (LPARAM)hWnd);

		return 0;
	}
	
	case WM_PAINT:
	{
		hDC = BeginPaint(hWnd,&ps);

		// 绘制背景色
		GetClientRect(hWnd,&rect);

		MyCGFillColor(hDC,&rect,0xCCCCCC);

		// 计算滑块大小,过小时不绘制
		if ((rect.bottom - rect.top) > 30 && scrollinfo.nMax && (scrollinfo.nMax - scrollinfo.nMin) >= (int)scrollinfo.nPage)
		{
			// 滑块大小计算
			// 滑块大小 = 滑块的高度/有效范围 * 每页的数量
			dbSize = max((float)((rect.bottom - rect.top)/ (scrollinfo.nMax - scrollinfo.nMin)* scrollinfo.nPage),20.0f);

			// 实际滑动位置 = (滚动条高度 - 滑块大小)/(有效范围 - 每页数量 + 1) * 当前行位置
			// 实际滚动的位置会比实际的少一页的数量

			nTop = 0;

			if (scrollinfo.nPos > 0)
				nTop = (int)((rect.bottom - rect.top - dbSize) / (scrollinfo.nMax - scrollinfo.nMin + 1 - scrollinfo.nPage ) * scrollinfo.nPos);

			// 由于精度问题,滑块可能会越界,越界就取最大值
			if (nTop && (nTop + (int)dbSize > rect.bottom))
			{
				nTop = rect.bottom - (int)dbSize;
			}

			rect.left++;
			rect.right--;
			rect.top = nTop;
			rect.bottom = rect.top + (int)dbSize;

			MyCGFillColor(hDC, &rect,nDragState ? 0x999999 : 0x666666e);
			InflateRect(&rect,-1,-1);
			MyCGFillColor(hDC, &rect, nDragState ? 0x666666e : 0x999999);
		}
		EndPaint(hWnd,&ps);
		break;
	}
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK MyTestWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	TEXTMETRIC tm;
	SCROLLINFO si;

	// These variables are required to display text. 
	static int xClient;     // width of client area 
	static int yClient;     // height of client area 
	static int xClientMax;  // maximum width of client area 

	static int xChar;       // horizontal scrolling unit 
	static int yChar;       // vertical scrolling unit 
	static int xUpper;      // average width of uppercase letters 

	static int xPos;        // current horizontal scrolling position 
	static int yPos;        // current vertical scrolling position 

	int i;                  // loop counter 
	int x, y;               // horizontal and vertical coordinates

	int FirstLine;          // first line in the invalidated area 
	int LastLine;           // last line in the invalidated area 
	HRESULT hr;
	size_t abcLength;        // length of an abc[] item 

	// Create an array of lines to display. 
#define LINES 28 
	static const TCHAR* abc[] = {
		   TEXT("anteater"),  TEXT("bear"),      TEXT("cougar"),
		   TEXT("dingo"),     TEXT("elephant"),  TEXT("falcon"),
		   TEXT("gazelle"),   TEXT("hyena"),     TEXT("iguana"),
		   TEXT("jackal"),    TEXT("kangaroo"),  TEXT("llama"),
		   TEXT("moose"),     TEXT("newt"),      TEXT("octopus"),
		   TEXT("penguin"),   TEXT("quail"),     TEXT("rat"),
		   TEXT("squid"),     TEXT("tortoise"),  TEXT("urus"),
		   TEXT("vole"),      TEXT("walrus"),    TEXT("xylophone"),
		   TEXT("yak"),       TEXT("zebra"),
		   TEXT("This line contains words, but no character. Go figure."),
		   TEXT("")
	};

	switch (uMsg)
	{
	case WM_CREATE:
		// Get the handle to the client area's device context. 
		hdc = GetDC(hWnd);

		// Extract font dimensions from the text metrics. 
		GetTextMetrics(hdc, &tm);
		xChar = tm.tmAveCharWidth;
		xUpper = (tm.tmPitchAndFamily & 1 ? 3 : 2) * xChar / 2;
		yChar = tm.tmHeight + tm.tmExternalLeading;

		// Free the device context. 
		ReleaseDC(hWnd, hdc);

		// Set an arbitrary maximum width for client area. 
		// (xClientMax is the sum of the widths of 48 average 
		// lowercase letters and 12 uppercase letters.) 
		xClientMax = 48 * xChar + 12 * xUpper;

		return 0;

	case WM_SIZE:

		// Retrieve the dimensions of the client area. 
		yClient = HIWORD(lParam);
		xClient = LOWORD(lParam);

		// Set the vertical scrolling range and page size
		si.cbSize = sizeof(si);
		si.fMask = SIF_RANGE | SIF_PAGE;
		si.nMin = 0;
		si.nMax = LINES - 1;
		si.nPage = yClient / yChar;
		SetScrollInfo(hWnd, SB_VERT, &si, TRUE);

		// Set the horizontal scrolling range and page size. 
		si.cbSize = sizeof(si);
		si.fMask = SIF_RANGE | SIF_PAGE;
		si.nMin = 0;
		si.nMax = 2 + xClientMax / xChar;
		si.nPage = xClient / xChar;
		SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);

		return 0;
	case WM_HSCROLL:
		// Get all the vertial scroll bar information.
		si.cbSize = sizeof(si);
		si.fMask = SIF_ALL;

		// Save the position for comparison later on.
		GetScrollInfo(hWnd, SB_HORZ, &si);
		xPos = si.nPos;
		switch (LOWORD(wParam))
		{
			// User clicked the left arrow.
		case SB_LINELEFT:
			si.nPos -= 1;
			break;

			// User clicked the right arrow.
		case SB_LINERIGHT:
			si.nPos += 1;
			break;

			// User clicked the scroll bar shaft left of the scroll box.
		case SB_PAGELEFT:
			si.nPos -= si.nPage;
			break;

			// User clicked the scroll bar shaft right of the scroll box.
		case SB_PAGERIGHT:
			si.nPos += si.nPage;
			break;

			// User dragged the scroll box.
		case SB_THUMBTRACK:
			si.nPos = si.nTrackPos;
			break;

		default:
			break;
		}

		// Set the position and then retrieve it.  Due to adjustments
		// by Windows it may not be the same as the value set.
		si.fMask = SIF_POS;
		SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
		GetScrollInfo(hWnd, SB_HORZ, &si);

		// If the position has changed, scroll the window.
		if (si.nPos != xPos)
		{
			ScrollWindow(hWnd, xChar * (xPos - si.nPos), 0, NULL, NULL);
		}

		return 0;

	case WM_VSCROLL:
		// Get all the vertial scroll bar information.
		si.cbSize = sizeof(si);
		si.fMask = SIF_ALL;
		GetScrollInfo(hWnd, SB_VERT, &si);

		// Save the position for comparison later on.
		yPos = si.nPos;
		switch (LOWORD(wParam))
		{

			// User clicked the HOME keyboard key.
		case SB_TOP:
			si.nPos = si.nMin;
			break;

			// User clicked the END keyboard key.
		case SB_BOTTOM:
			si.nPos = si.nMax;
			break;

			// User clicked the top arrow.
		case SB_LINEUP:
			si.nPos -= 1;
			break;

			// User clicked the bottom arrow.
		case SB_LINEDOWN:
			si.nPos += 1;
			break;

			// User clicked the scroll bar shaft above the scroll box.
		case SB_PAGEUP:
			si.nPos -= si.nPage;
			break;

			// User clicked the scroll bar shaft below the scroll box.
		case SB_PAGEDOWN:
			si.nPos += si.nPage;
			break;

			// User dragged the scroll box.
		case SB_THUMBTRACK:
			si.nPos = si.nTrackPos;
			break;

		default:
			break;
		}

		// Set the position and then retrieve it.  Due to adjustments
		// by Windows it may not be the same as the value set.
		si.fMask = SIF_POS;
		SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
		GetScrollInfo(hWnd, SB_VERT, &si);

		// If the position has changed, scroll window and update it.
		if (si.nPos != yPos)
		{
			ScrollWindow(hWnd, 0, yChar * (yPos - si.nPos), NULL, NULL);
			UpdateWindow(hWnd);
		}

		return 0;

	case WM_PAINT:
		// Prepare the window for painting.
		hdc = BeginPaint(hWnd, &ps);

		// Get vertical scroll bar position.
		si.cbSize = sizeof(si);
		si.fMask = SIF_POS;
		GetScrollInfo(hWnd, SB_VERT, &si);
		yPos = si.nPos;

		// Get horizontal scroll bar position.
		GetScrollInfo(hWnd, SB_HORZ, &si);
		xPos = si.nPos;

		// Find painting limits.
		FirstLine = max(0, yPos + ps.rcPaint.top / yChar);
		LastLine = min(LINES - 1, yPos + ps.rcPaint.bottom / yChar);

		for (i = FirstLine; i <= LastLine; i++)
		{
			x = xChar * (1 - xPos);
			y = yChar * (i - yPos);

			// Note that "55" in the following depends on the 
			// maximum size of an abc[] item. Also, you must include
			// strsafe.h to use the StringCchLength function.
			hr = StringCchLength(abc[i], 55, &abcLength);
			if ((FAILED(hr)) | (abcLength == NULL))
			{
				//
				// TODO: write error handler
				//
			}

			// Write a line of text to the client area.
			TextOut(hdc, x, y, abc[i], abcLength);
		}

		// Indicate that painting is finished.
		EndPaint(hWnd, &ps);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件，或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来，若要再次打开此项目，请转到“文件”>“打开”>“项目”并选择 .sln 文件
